Skip to content

feat: Experimental annotated argparse#1666

Merged
tleonhardt merged 29 commits into
mainfrom
feat/annotated-argparse
Jun 2, 2026
Merged

feat: Experimental annotated argparse#1666
tleonhardt merged 29 commits into
mainfrom
feat/annotated-argparse

Conversation

@KelvinChung2000
Copy link
Copy Markdown
Contributor

Adds @with_annotated, a type-hint-driven alternative to @with_argparser that builds the parser automatically from a command's signature (positional/option inference, enum/literal/path/collection handling, subcommands, groups, mutex). Marked experimental.

  • New module cmd2/annotated.py plus Argument / Option metadata classes exported from cmd2
  • Underscored param names auto-dasherize in generated flags (dry_run--dry-run); opt out via Option("--my_flag")
  • Dedicated docs page docs/features/annotated.md with an experimental admonition; argument_processing.md keeps a short pointer
  • Example app examples/annotated_example.py and test suite in tests/test_annotated.py

Adds @with_annotated decorator that builds argparse parsers from type-annotated
function signatures. Supports Annotated[T, Argument(...)] / Annotated[T, Option(...)]
metadata, automatic positional/option detection, optional unwrapping, collections,
enums, literals, Path completion, subcommands via subcommand_to=, base_command=True
with cmd2_handler dispatch, and argument/mutually-exclusive groups.

- New module cmd2/annotated.py with Argument, Option, with_annotated, and
  build_parser_from_function helpers
- Comprehensive test suite in tests/test_annotated.py
- Example in examples/annotated_example.py
- Docs updates in docs/features/argument_processing.md
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.50%. Comparing base (e2c061c) to head (08f027e).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1666      +/-   ##
==========================================
- Coverage   99.53%   99.50%   -0.03%     
==========================================
  Files          22       23       +1     
  Lines        4928     5689     +761     
==========================================
+ Hits         4905     5661     +756     
- Misses         23       28       +5     
Flag Coverage Δ
unittests 99.50% <100.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tleonhardt
Copy link
Copy Markdown
Member

I plan to review this PR this weekend. @kmvanbrunt @bambu I'd very much appreciate your feedback as well.

@tleonhardt tleonhardt added this to the 4.0.0 milestone May 15, 2026
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread examples/annotated_example.py
Comment thread cmd2/annotated.py Outdated
@kmvanbrunt
Copy link
Copy Markdown
Member

@KelvinChung2000

How can I do the following?

  1. Set the title and description for an argument group?
  2. Set the description and epilog for a parser?
  3. Set a custom help formatter class for a parser?
  4. Use a custom parser class?

Comment thread cmd2/__init__.py Outdated
KelvinChung2000 and others added 8 commits May 19, 2026 15:14
- aliases param: Sequence[str] = () to match as_subcommand_to()
- update aliases None checks now that it defaults to ()
- type with_annotated via @overload (no longer untyped decorator)
- drop experimental annotated exports from cmd2/__init__.py
- import from cmd2.annotated in example/tests/docs; fix example mypy
- Group(*members, title=, description=) for titled argument-group
sections
  (groups= now accepts bare tuples or Group)
- description= and epilog= for the generated parser
- formatter_class= for a custom help formatter
- parser_class= for a custom parser class

Includes tests, example command, and docs.
Adds VerbatimHelpFormatter and StrictArgumentParser subclasses and a
do_report command so all four parser-customization features have a
runnable demonstration, not just docs/tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop bare tuple[str, ...] support; entries must be Group instances.
Removes the _group_members shim and adds an explicit TypeError guard
(_require_group) so a wrong type fails clearly instead of with an
AttributeError. Updates tests and docs accordingly.
Adds a do_export command so mutually_exclusive_groups has a runnable
demo alongside the existing groups demo.
Comment thread cmd2/annotated.py Outdated
@tleonhardt tleonhardt changed the title feat: Experiemtal annotated argparse feat: Experimental annotated argparse May 20, 2026
Comment thread cmd2/annotated.py
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
@KelvinChung2000
Copy link
Copy Markdown
Contributor Author

I have updated the implementation to be as declarative as possible. So, in terms of maintenance or edge cases, you can add/ban them by just adding a new rule.

Hopefully, I have now covered most of the missing features.

@KelvinChung2000
Copy link
Copy Markdown
Contributor Author

Actually, do you want me to split the mega file and store it under something like cmd2/annotated? I can move out some of the "noise" like changes to potentially make things easier to review. This sort of large diff commit, GitHub is just not that well designed for it.

@tleonhardt
Copy link
Copy Markdown
Member

@KelvinChung2000 I'm flexible. I can see an argument for keeping it all self-contained within one file.

But I could also see the argument for splitting it into multiple files underneath the cmd2.annotated namespace - basically into its own sub-package.

@kmvanbrunt Would you have a preference here?

Comment thread docs/features/argument_processing.md Outdated
Comment thread cmd2/annotated.py Outdated
@tleonhardt
Copy link
Copy Markdown
Member

@KelvinChung2000 I chatted with @kmvanbrunt - for now please leave everything in the one big file annotated.py. Later if things grow, we can consider breaking up into multiple files.

Also:
- Fix minor edge case silent failure bug
- Add description of this new feature to CHANGELOG under an "Experimental Features" section for the 4.0.0 release
@tleonhardt
Copy link
Copy Markdown
Member

@KelvinChung2000 I pushed a change to add @with_annotated to the top level namespace after chatting with @kmvanbrunt about it.

I also pushed a fix for the minor edge case bug I mentioned last time.

From my perspective, this is good to merge. If anyone has any further comments or recommendations, put them forth sooner rather than later. I'll merge this a day or two from now if there are no further suggestions.

tleonhardt
tleonhardt previously approved these changes May 30, 2026
tleonhardt
tleonhardt previously approved these changes May 31, 2026
…ed as the leaf command

If a subcommand is also a `base_command` (e.g., intermediate subcommands), has `subcommand_required=False`, and is executed without any further subcommands provided by the user, it becomes the leaf command. The `cmd_wrapper` of the top-level command will set `ns.cmd2_handler` to point to this nested `handler` itself. Extracting it through `_filtered_namespace_kwargs` and passing it to the command function causes the function to call its own handler repeatedly via `if cmd2_handler: cmd2_handler()`, leading to an infinite recursion (stack overflow).

This commit adds code to detect and short-circuit that potential infinite recursion.

Also: Bump a
Document that this feature works in a manner similar to Typer to help make it more discoverable for users.
@tleonhardt
Copy link
Copy Markdown
Member

@KelvinChung2000 Thank you for bearing with us on the long road to get here and congrats on getting your first cmd2 feature merged in!

@tleonhardt tleonhardt merged commit 9c5478c into main Jun 2, 2026
28 of 29 checks passed
@tleonhardt tleonhardt deleted the feat/annotated-argparse branch June 2, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants